component extends="mxunit.framework.TestCase"
{
	property name="mapper";
		
	import com.firemoss.atonement.mapper.*;
	import com.firemoss.atonement.mapper.resolution.*;
	import com.firemoss.atonement.test.mapper.objects.*;
	import com.firemoss.atonement.test.metadata.inspectors.property.objects.*;
	
	public void function setup()
	{
		variables.mapper = new Mapper();
	}

	public void function mapper_supports_bulk_map_addition()
	{
		var mappings  = [
			new Mapping( new SimpleObject(), new SimpleObjectDTO() ),
			new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() )
		];
			
		mapper.setMappings( mappings );
			
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new SimpleObjectDTO()));
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new DotNetStyleSimpleObjectDTO()));
	}
	
	public function mapper_supports_fluent_style_map_addition()
	{
		mapper.addMapping(
			new Mapping( new SimpleObject(), new SimpleObjectDTO() )
		).addMapping(
			new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() )
		);
			
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new SimpleObjectDTO()));
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new DotNetStyleSimpleObjectDTO()));
	}
	
	public function map_table_can_be_reset() 
	{
		mapper.addMapping(
			new Mapping( new SimpleObject(), new SimpleObjectDTO() )
		).addMapping(
			new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() )
		);
			
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new SimpleObjectDTO()));
		assertTrue( mapper.mappingExistsBetween( new SimpleObject(), new DotNetStyleSimpleObjectDTO()));
		
		mapper.resetMapTable();
			
		assertFalse( mapper.mappingExistsBetween( new SimpleObject(), new SimpleObjectDTO()));
		assertFalse( mapper.mappingExistsBetween( new SimpleObject(), new DotNetStyleSimpleObjectDTO()));
	}
	
	public function can_get_a_mapping()
	{
		mapper.addMapping(
			new Mapping( new SimpleObject(), new SimpleObjectDTO() )
		);
		
		assertTrue( not isNull( mapper.getMappingFor( new SimpleObject(), new SimpleObjectDTO() ) ) );
	}
	
	public function properties_are_resolved_when_map_is_added()
	{
		var mapping = new Mapping( new SimpleObject(), new SimpleObjectDTO() );
		
		assertFalse( mapping.hasResolvedProperty( "getterProperty" ) );
			
		mapper.addMapping( mapping );
		
		assertTrue( mapping.hasResolvedProperty( "getterProperty" ) );
	}
	
	public function unresolved_properties_are_detected_when_map_is_added() 
	{
		var mapping = new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() );
		
		assertFalse( mapping.hasResolvedProperty( "getterProperty" ) );
			
		mapper.addMapping( mapping );
		
		assertTrue( mapping.hasUnresolvedProperty( "GetterProperty" ) );
	}
	
	public function a_custom_property_mapping_can_be_added()
	{
		var mapping = new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() );
		
		assertFalse( mapping.hasResolvedProperty( "GetterProperty" ), "GetterProperty was already resolved!" );
		
		mapper.addMapping( mapping );
		
		assertTrue( mapping.hasUnresolvedProperty( "GetterProperty" ), "GetterProperty was already resolved after adding mapping!" );
		
		mapping.addPropertyResolver( 
			new com.firemoss.atonement.mapper.PropertyResolver(
				"getterProperty", 
				"GetterProperty", 
				new com.firemoss.atonement.mapper.resolution.SimpleAssignmentValueResolver().resolve
			)
		);
		
		assertFalse( mapping.hasUnresolvedProperty( "GetterProperty" ),  "GetterProperty was still unresolved!" );
		assertTrue( mapping.hasResolvedProperty( "GetterProperty" ),  "GetterProperty not shown as resolved!" );
	}
	
	public function custom_property_mappings_can_be_used_in_bulk_mapping_definition()
	{
		var badMapping = new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() )
			.addPropertyResolver( 
				new PropertyResolver(
					"getterProperty", 
					"GetterProperty", 
					new SimpleAssignmentValueResolver().resolve 
				)
			);
		
		var mappings  = [ badMapping ];
			
		assertFalse( badMapping.hasResolvedProperty( "GetterProperty" ), "GetterProperty was already resolved"  );
		
		mapper.setMappings( mappings );
		
		assertTrue( badMapping.hasUnresolvedProperty( "PropertyProperty" ), "PropertyProperty wasn't unresolved" );
		assertFalse( badMapping.hasUnresolvedProperty( "GetterProperty" ),"GetterProperty was still unresolved" );
	}
	
	public function assert_mappings_are_valid_finds_currently_unresolved_properties()
	{
		var ex = "";
		var mapping = new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() );
		
		assertFalse( mapping.hasResolvedProperty( "GetterProperty" ) );
		
		mapper.addMapping( mapping );
		
		assertFalse( mapping.hasResolvedProperty( "GetterProperty" ), "GetterProperty was already resolved"  );
		
		try
		{
			mapper.assertMappingsAreValid();
		}
		catch (InvalidMappingsError e )
		{
			ex = e;
		}	
		
		assertFalse( isSimpleValue( ex ), "InvalidMappingsError wasn't thrown." );
		
	}
	
	public function target_properties_can_be_ignored_programatically()
	{
		mapper.addMapping( new Mapping( new Contact(), new ContactDTO(), false )
			.ignoreProperty( "ignoredProperty" )
			.setAggregatedType( "addressList", new AddressDTO() )
			.setAggregatedType( "addressDictionary", new AddressDTO() )
		);
		
		addValidAddressMapping( mapper );
		
		assertTrue( mapper.getMappingFor( new Contact(), new ContactDTO() ).assertMappingIsValid() );							
		
	}
	
	public function target_properties_can_be_ignored_declaratively()
	{
		var mapping = new Mapping( new Contact(), new ContactDTO(), false );
		
		mapping.setAggregatedPropertyTargetTypes( {
			"addressList" = new AddressDTO(),
			"addressDictionary" = new AddressDTO()
		} );
			
		mapping.setIgnoredProperties( [ "ignoredProperty" ] );
		
		mapper.setMappings( [ mapping ] );
		
		addValidAddressMapping( mapper );
		
		assertTrue( mapper.getMappingFor( new Contact(), new ContactDTO() ).assertMappingIsValid() );							
		
	}
	
	/**
	 * We should be able to define a mapping between two things where a map-based resolver is needed,
	 * define the required additional mapping _after_ the initial mapping, and still be considered valid.
	 */
	public function late_type_mapping_is_supported()
	{
		mapper.addMapping( new Mapping( new Contact(), new ContactDTO() )
			.ignoreProperty( "ignoredProperty" )
			.setAggregatedType( "addressList", new AddressDTO() )
			.setAggregatedType( "addressDictionary", new AddressDTO() )
		);

		addValidAddressMapping( mapper );
		
		assertTrue( mapper.getMappingFor( new Contact(), new ContactDTO() ).assertMappingIsValid() );							
		
	}
	
	public function mapping_asserts_validity_before_executing() 
	{
		var ex = "";
		var mapping = new Mapping( new SimpleObject(), new DotNetStyleSimpleObjectDTO() );
		
		assertFalse( mapping.hasResolvedProperty( "GetterProperty" ) );
		
		mapper.addMapping( mapping );
		
		assertFalse( mapping.hasResolvedProperty( "GetterProperty" ), "GetterProperty was already resolved"  );
		
		try
		{
			mapper.map( new SimpleObject(), new DotNetStyleSimpleObjectDTO() );
		}
		catch (InvalidMappingsError e )
		{
			ex = e;
		}	
		
		assertFalse( isSimpleValue( ex ), "InvalidMappingsError wasn't thrown." );
	}
	
	public function simple_mapping_works()
	{
		addValidAddressMapping( mapper );
		
		var addy = createAddress();
			
		var dto = new AddressDTO();
			
		mapper.map( addy, dto );
		
		assertTrue( dto.getline1() eq "line 1 value" );
		assertTrue( dto.getline2() eq "line 2 value" );
		assertTrue( dto.getcity() eq "city value" );
		assertTrue( dto.getaddressAsOneLine() eq "line 1 value line 2 value" );
		assertTrue( dto.getstate() eq "CODE" );
	}
	
	public function singular_aggregate_mapping_of_mapped_classes_works()
	{
		addValidContactMapping( mapper );
		
		var contact = createContact();
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
			
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
	}
	
	public function list_mapping_works()
	{
		addValidContactMapping( mapper );
		
		var addy1 = createAddress().set({
					line1 = "nested1"
				});
		var addy2 =
				createAddress().set({
					line1 = "nested2"
				});

		
		var contact = createContact().set({
			addressList = [addy1, addy2]
		});
		
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
		
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
		assertEquals( 2, arrayLen(dto.getaddressList()) );
		assertTrue( IsInstanceOf( dto.getaddressList()[2], "com.firemoss.atonement.test.mapper.objects.AddressDTO" ), "List of addresses are not AddressDTO instances." );
		assertTrue( dto.getaddressList()[2].getline1() eq "nested2" );
	}
	
	public function explicit_list_mapping_works() 
	{
		addValidAddressMapping( mapper ).addMapping( new Mapping( new Contact(), new ContactDTO() )
			.ignoreProperty( "ignoredProperty" )
			.setAggregatedType( "addressDictionary", new AddressDTO() )
			.addPropertyResolver( 
				new PropertyResolver(
					"addressList", 
					"addressList", 
					this,
					"explicit_list_mapping_resolver"
				)
			)
		);
		
		var contact = createContact().set({
			addressList = ["one", "two"]
		});
		
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
		assertEquals( 2, arrayLen(dto.getaddressList()) );
		assertTrue( dto.getaddressList()[2] eq 2 );
		
	}
	
	public array function explicit_list_mapping_resolver( source, targetProperty, mapping, mappingChain ) 
	{
		return [ 1, 2 ];
	} 
	
	public function dictionary_mapping_works()
	{
		addValidContactMapping( mapper );
		
		var dict = structNew();
		
		dict[ "addy1" ] = createAddress().set({
			line1 = "nestedDict1"
		});
		dict[ "addy2" ] = createAddress().set({
			line1 = "nestedDict2"
		});

		var contact = createContact().set({
			addressDictionary = dict
		});
		
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
		assertTrue( dto.getAddressDictionary()["addy2"].getline1() eq "nestedDict2" );
	}
	
	public function maps_can_be_implicitly_created()
	{
		addValidContactMapping( mapper );
		
		var addy1 = createAddress().set({
					line1 = "nested1"
				});
		var addy2 =
				createAddress().set({
					line1 = "nested2"
				});

		
		var contact = createContact().set({
			addressList = [addy1, addy2]
		});
		
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
		
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
		assertEquals( 2, arrayLen(dto.getaddressList()) );
		assertTrue( dto.getaddressList()[2].getline1() eq "nested2" );	
	}
	/*
	
	[Test]
	public function declarative_mapping_configuration_works_fully() : void
	{
		var mapper : Mapper = new MappingDefinitions().mapper
			
		mapper.assertMappingsAreValid()
			
		fullMappingAssertions( mapper )
	}
	*/
	
	public function implicit_mapping_configuration_works_fully() 
	{
		var mapper = new Mapper();
		fullMappingAssertions( mapper );
	}
	/*
	
	[Test]
	public function programmatic_mapping_configuration_works_fully() : void
	{
		var mapper : Mapper = new Mapper()
		
		mapper = new Mapper()
			.addMapping(
				new Mapping( Contact, ContactDTO, false )
					.setAggregatedType( "addressList", AddressDTO )
					.setAggregatedType( "addressDictionary", AddressDTO )
					.ignoreProperty( "ignoredProperty" )
				)
			.addMapping(
				new Mapping( Address, AddressDTO, false )
					.addPropertyResolver(
						new PropertyResolver( "state", "state", new AddressDTO().stateCode )
					)
					.addPropertyResolver(
						new PropertyResolver( null, "addressAsOneLine", new AddressDTO().oneLineAddress )
					)
			)

		mapper.assertMappingsAreValid()
			
		fullMappingAssertions( mapper )
	}
	*/
	
	// HELPERS
	
	private function fullMappingContact()
	{
		var dict = structNew();
		dict[ "addy1" ] = createAddress().set({
			line1 = "nestedDict1"
		});
		dict[ "addy2" ] = createAddress().set({
			line1 = "nestedDict2"
		});
		
		
		var addy1 = createAddress().set({
					line1 = "nested1"
				});
		var addy2 =
				createAddress().set({
					line1 = "nested2"
				});

		var addressList = [ addy1, addy2 ];
		
		return createContact().set({
			addressDictionary = dict,
			addressList = addressList
		});
	}
	
	private function fullMappingAssertions( mapper )
	{
		var contact = fullMappingContact();
		var dto = new ContactDTO();
		
		mapper.map( contact, dto );
		
		assertTrue( dto.getfirstName() eq "John" );
		assertTrue( dto.getprimaryAddress().getstate() eq "CODE" );
		assertEquals( 2, arrayLen(dto.getaddressList()) );
		assertTrue( dto.getaddressList()[2].getline1() eq "nested2" );
		assertTrue( dto.getaddressDictionary()[ "addy2" ].getline1() eq "nestedDict2" );
	}
	
	private function addValidAddressMapping( mapper  ) 
	{
		return mapper.addMapping( new Mapping( new Address(), new AddressDTO() ) );
	}
	
	private function addValidContactMapping( mapper ) 
	{
		return addValidAddressMapping( mapper ).addMapping( new Mapping( new Contact(), new ContactDTO() ) );
	}
	
	private function createAddress()
	{
		 var state = new State( { code = "CODE", name = "Name" } );

		return new Address({
			line1 = "line 1 value",
			line2 = "line 2 value",
			city = "city value",
			postalCode = "zip value",
			state = state
		});
	}
	
	private function createContact() 
	{
		return new Contact({
			firstName = "John",
			lastName = "Doe",
			primaryAddress = createAddress()
		});
	}
}